package com.tjhk.netty;

import com.tjhk.netty.codec.MessageDecoder;
import com.tjhk.netty.codec.MessageDecoderWrapper;
import com.tjhk.netty.codec.MessageEncoder;
import com.tjhk.netty.codec.MessageEncoderWrapper;
import com.tjhk.netty.mapping.DefaultHandlerRegister;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class NettyServer {

    private static final Logger log = LoggerFactory.getLogger(NettyServer.class.getSimpleName());

    protected final int port;
    protected final int maxFrameLength;
    protected final byte[] delimiter;
    protected final long readerIdleTime;
    protected final MessageDecoder messageDecoder;
    protected final MessageEncoder messageEncoder;
    protected final String packageName;
    public NettyServer(int port,
                       int maxFrameLength,
                       byte[] delimiter,
                       long readerIdleTime,
                       MessageDecoder messageDecoder,
                       MessageEncoder messageEncoder,
                       String packageName) {
        this.port = port;
        this.maxFrameLength = maxFrameLength;
        this.delimiter = delimiter;
        this.readerIdleTime = readerIdleTime;
        this.messageDecoder = messageDecoder;
        this.messageEncoder = messageEncoder;
        this.packageName = packageName;
    }
    public static class Builder {
        private int port;
        private int maxFrameLength;
        private byte[] delimiter;
        private long readerIdleTime;
        private MessageDecoder messageDecoder;
        private MessageEncoder messageEncoder;
        private String packageName;
        public Builder() {
        }

        public Builder setPort(int port) {
            this.port = port;
            return this;
        }

        public Builder setMaxFrameLength(int maxFrameLength) {
            this.maxFrameLength = maxFrameLength;
            return this;
        }

        public Builder setDelimiter(byte[] delimiter) {
            this.delimiter = delimiter;
            return this;
        }

        public Builder setReaderIdleTime(long readerIdleTime) {
            this.readerIdleTime = readerIdleTime;
            return this;
        }

        public Builder setMessageDecoder(MessageDecoder messageDecoder) {
            this.messageDecoder = messageDecoder;
            return this;
        }

        public Builder setMessageEncoder(MessageEncoder messageEncoder) {
            this.messageEncoder = messageEncoder;
            return this;
        }

        public Builder setPackageName(String packageName) {
            this.packageName = packageName;
            return this;
        }

        public NettyServer build() {
            return new NettyServer(
                    this.port,
                    this.maxFrameLength,
                    this.delimiter,
                    this.readerIdleTime,
                    this.messageDecoder,
                    this.messageEncoder,
                    this.packageName);
        }
    }
    public void run() throws Exception {
        // 创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
        // bossGroup只是处理连接请求 ,真正的和客户端业务处理，会交给workerGroup完成
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());
        try {
            // 创建服务器端的启动对象
            ServerBootstrap b = new ServerBootstrap();
            // 使用链式编程来配置参数
            b.group(bossGroup, workerGroup) //设置两个线程组
                    // 使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    //创建通道初始化对象，设置初始化参数，在 SocketChannel 建立起来之前执行
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel channel) throws Exception {
                            //对workerGroup的SocketChannel设置处理器
                            channel.pipeline()
                                    // 未响应连接保持时间
                                    .addLast(new IdleStateHandler(readerIdleTime, 0, 0, TimeUnit.MINUTES))
                                    // 最大数据长度和分隔符
                                    .addLast(new DelimiterBasedFrameDecoder(maxFrameLength,
                                            Unpooled.copiedBuffer(Unpooled.copiedBuffer(delimiter))))
                                    .addLast(new MessageDecoderWrapper(messageDecoder))
                                    .addLast(new MessageEncoderWrapper(messageEncoder))
                                    .addLast(new NettyServerHandler(new DefaultHandlerRegister(packageName)));
                        }
                    })
                    // 初始化服务器连接队列大小，服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
                    // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(NioChannelOption.SO_BACKLOG, 1024)
                    .option(NioChannelOption.SO_REUSEADDR, true)
                    .childOption(NioChannelOption.TCP_NODELAY, true);
            log.info("===========服务端启动=========");
            // 绑定一个端口并且同步, 生成了一个ChannelFuture异步对象，通过isDone()等方法可以判断异步事件的执行情况
            // 启动服务器(并绑定端口)，bind是异步操作，sync方法是等待异步操作执行完毕
            ChannelFuture cf = b.bind(port).sync();
            // 给cf注册监听器，监听我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        log.info("服务端启动成功,监听端口:{}",port);
                    } else {
                        log.error("服务端启动失败,监听端口:{}",port);
                    }
                }
            });
            // 等待服务端监听端口关闭，closeFuture是异步操作
            // 通过sync方法同步等待通道关闭处理完毕，这里会阻塞等待通道关闭完成，内部调用的是Object的wait()方法
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
